package clue.players.ai;
import java.util.LinkedList;
import java.util.ListIterator;

import clue.cards.Card;
import clue.cards.RoomCard;
import clue.cards.SuspectCard;
import clue.cards.WeaponCard;
import clue.engine.Deck;
import clue.enums.Room;
import clue.enums.Suspect;
import clue.enums.Weapon;
/**
 * This only works for 3 players
 *
 * @author Shawn
 */
public class EnumerationEngine {

  //
  // [ ROOM, SUSPECT, WEAPON ]
  //

  private long ourHand;

  //
  // tally[0] -> in opp 1's hand
  // tally[1] -> in opp 2's hand
  // tally[3] -> in env
  //

  private int[][] tally;
  private double[][] prob;
  private LinkedList<Long> particles;

  public EnumerationEngine() {
    tally = new int[3][21];
    prob = new double[3][21];
    particles = new LinkedList<Long>();
  }

  public void ourHand(Card[] cards) {
    ourHand = convertCardsToLong(cards);

    for (long w = 1l; w < (1l << 6); w <<= 1) {
      // if it's in our hand, skip it
      if ((ourHand & w) != 0)
        continue;

      for (long s = (1l << 6); s < (1l << 12); s <<= 1) {
        if ((ourHand & s) != 0)
          continue;

        for (long r = (1l << 12); r < (1l << 21); r <<= 1) {
          if ((ourHand & r) != 0)
            continue;

          long remaining = ~(ourHand | w | s | r) & 0x1FFFFF;
          choose(6, 0, 0, 1l, remaining, 0, w | s | r);
        }
      }
    }

    updateProb();
  }

  public double getProb(Card c, int idx) {
    return prob[idx][convertCardToIdx(c)];
  }

  //
  // Whenever we make a suggestion and someone shows us a Card to refute it,
  // we must remove all shuffles where that person does not contain said Card
  //

  public void mustExist(Card c, int opp) {
    long l = convertCardToLong(c) << (opp * 21);

    // if l & particle == 0, then particle is invalid
    ListIterator<Long> li = particles.listIterator();
    while (li.hasNext()) {
      long particle = li.next();
      if ((l & particle) == 0) {
        decrementTally(particle);
        li.remove();
      }
    }

    updateProb();
  }

  //
  // Whenever an opponent fails to refute our suggestion, we know
  // said person cannot have ANY of the 3 cards
  //

  public void mustNotExist(Card[] cards, int opp) {
    long suggestion = convertCardsToLong(cards) << (opp * 21);

    ListIterator<Long> li = particles.listIterator();
    while (li.hasNext()) {
      long particle = li.next();
      if ((suggestion & particle) != 0) {
        decrementTally(particle);
        li.remove();
      }
    }

    updateProb();
  }

  //
  // This reflects what the other two opponents do to each other
  //   <opp> made a suggestion containing <cards>
  //   if <refuted>, then other opponent must have one of the cards
  //   else, then other opponent must not have any of the cards
  //

  public void opponentReaction(Card[] cards, int opp, boolean refuted) {
    int otherOpp = (opp == 0) ? 1 : 0;
    long suggestion = convertCardsToLong(cards) << (otherOpp * 21);

    ListIterator<Long> li = particles.listIterator();
    while (li.hasNext()) {
      long particle = li.next();


      if ((refuted && ((particle & suggestion) == 0)) ||
          (!refuted && (particle & suggestion) != 0)) {
        // if other opponent refuted, which means other opponent must have at
        // *one* of the suggestion cards. This means that particle & suggestion
        // != 0
        // if other opponent did not refute, which means other opponent does not
        // have *any* of the suggestion cards, which means particle & suggestion
        // == 0.
        decrementTally(particle);
        li.remove();
      }
    }

    updateProb();
  }

  private void updateProb() {
    for (int i = 0; i < 21; i++) {
      int sum = tally[0][i] + tally[1][i] + tally[2][i];
      if (sum == 0) {
        continue;
      }

      for (int j = 0; j < 3; j++) {
        prob[j][i] = ((double)tally[j][i]) / sum;
      }
    }
  }

  private void incrementTally(long hand, int tallyIdx) {
    while (hand != 0) {
      long bit = hand & ~(hand - 1);
      int pos = MultiplyDeBruijnBitPosition2[((int)bit * 0x077CB531) >>> 27];
      assert (pos <= 21);
      tally[tallyIdx][pos]++;
      hand ^= bit;
    }
  }

  private void incrementTally(long particle) {
    incrementTally(particle & 0x1FFFFF, 0);
    incrementTally((particle >>> 21) & 0x1FFFFF, 1);
    incrementTally((particle >>> 42) & 0x1FFFFF, 2);
  }

  private void decrementTally(long hand, int tallyIdx) {
    while (hand != 0) {
      long bit = hand & ~(hand - 1);
      int pos = MultiplyDeBruijnBitPosition2[((int)bit * 0x077CB531) >>> 27];
      assert (pos <= 21);
      tally[tallyIdx][pos]--;
      hand ^= bit;
    }
  }

  private void decrementTally(long particle) {
    decrementTally(particle & 0x1FFFFF, 0);
    decrementTally((particle >>> 21) & 0x1FFFFF, 1);
    decrementTally((particle >>> 42) & 0x1FFFFF, 2);
  }

  private void choose(int k, int c, int enc, long idx, long remaining,
                       long hand, long env) {
    if (c >= k) {
      // we have chosen all k
      // calculate opp2's hand
      long particle = constructParticle(hand, hand ^ remaining, env);
      incrementTally(particle);

      // add to particle list
      particles.add(particle);
      return;
    } else if (idx >= (1l << 21))
      throw new RuntimeException("idx out of bounds");

    int numToStop = 12 - k + c + 1;
    for (; idx < (1l << 21); idx <<= 1) {
      // skip the 0's
      if ((idx & remaining) == 0)
        continue;

      enc++;
      choose(k, c + 1, enc, idx << 1, remaining, hand | idx, env);
      if (enc >= numToStop)
        break;
    }
  }

  private static final int[] MultiplyDeBruijnBitPosition2 = {
    0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
    31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
  };

  private long constructParticle(long opp1, long opp2, long env) {
    return opp1 | (opp2 << 21) | (env << 42);
  }

  private Card convertLongToCard(long v) {
    int pos = MultiplyDeBruijnBitPosition2[((int)v * 0x077CB531) >>> 27];

    // 0 <= pos <= 20

    if (pos < 6) {
      // weapon
      return new WeaponCard(Weapon.values()[pos]);
    } else if (pos < 12) {
      pos -= 6;
      return new SuspectCard(Suspect.values()[pos]);
    } else if (pos < 21) {
      pos -= 12;
      return new RoomCard(Room.values()[pos]);
    }

    return null;
  }

  private int convertCardToIdx(Card c) {
    if (c instanceof WeaponCard) {
      switch (((WeaponCard) c).getWeapon()) {
        case CANDLESTICK: return 0;
        case DAGGER: return 1;
        case LEAD_PIPE: return 2;
        case REVOLVER: return 3;
        case ROPE: return 4;
        case WRENCH: return 5;
      }
    } else if (c instanceof SuspectCard) {
      switch (((SuspectCard) c).getSuspect()) {
        case CM: return 6;
        case MP: return 7;
        case MS: return 8;
        case MW: return 9;
        case PP: return 10;
        case RG: return 11;
      }
    } else if (c instanceof RoomCard) {
      switch (((RoomCard) c).getRoom()) {
        case BALLROOM: return 12;
        case BILLARD_ROOM: return 13;
        case CONSERVATORY: return 14;
        case DINING_ROOM: return 15;
        case HALL: return 16;
        case KITCHEN: return 17;
        case LIBRARY: return 18;
        case LOUNGE: return 19;
        case STUDY: return 20;
      }
    }

    return -1;
  }

  private long convertCardToLong(Card c) {
    return 1l << convertCardToIdx(c);
  }

  private long convertCardsToLong(Card[] cards) {
    long ret = 0;
    for (Card c : cards)
      ret ^= convertCardToLong(c);

    return ret;
  }

  public static void main(String[] args) {
    EnumerationEngine e = new EnumerationEngine();
    e.ourHand(new Card[] { new RoomCard(Room.BALLROOM),
                           new RoomCard(Room.HALL),
                           new RoomCard(Room.KITCHEN),
                           new RoomCard(Room.LIBRARY),
                           new WeaponCard(Weapon.CANDLESTICK),
                           new SuspectCard(Suspect.CM)});

    e.mustExist(new SuspectCard(Suspect.MP), 0);
    e.mustExist(new SuspectCard(Suspect.MW), 1);
    e.mustNotExist(new Card[] { new RoomCard(Room.CONSERVATORY),
                                new WeaponCard(Weapon.WRENCH),
                                new SuspectCard(Suspect.MS)},
                                0);

    e.opponentReaction(new Card[] { new RoomCard(Room.CONSERVATORY),
                                    new WeaponCard(Weapon.WRENCH),
                                    new SuspectCard(Suspect.MS)},
                                    0, true);

    /*Deck d = new Deck();
    d.mixCards();
    Card c;
    while ((c = d.removeMixedCard()) != null) {
      for (int i = 0; i < 3; i++) {
        System.out.println(c + ":" + e.getProb(c, i));
      }
    }*/
  }
}
